package com.nekonomics
package domain.service

import api.dto.response.{PageResponseDto, SkuCardDto}
import domain.detail.{SkuDetail, SkuSpecDetail}
import domain.entity.{OrderItem, Sku, Spu}
import error.ProductError
import persistence.query.{SkuDetailRepo, SkuSpecDetailRepo}
import persistence.repository.{SkuRepo, SpuRepo}
import utils.DoobieUtil.*

import doobie.hikari.HikariTransactor
import zio.*

trait ProductService {
  def homePageList(pageNum: Int, pageSize: Int): DomainIO[PageResponseDto[SkuCardDto]]

  def getSkuAndSpecs(skuId: Long): DomainIO[(Sku, List[SkuSpecDetail])]

  def getStock(skuId: Long): DomainIO[Int]

  def getSpecs(skuId: Long): DomainIO[List[SkuSpecDetail]]

  def getSkuDetail(skuId: Long): DomainIO[SkuDetail]

  def getSpu(skuId: Long): DomainIO[Spu]

  def getSkuIds4Spu(spuId: Long): DomainIO[List[Long]]

  def getSkus4Spu(spuId: Long): DomainIO[List[(Sku, List[SkuSpecDetail])]]

}

object ProductService {
  val live: TaskLayer[ProductService] = (config.layers ++ persistence.layers) >>> ZLayer.fromFunction(SkuServiceLive.apply)
}


case class SkuServiceLive(
  xa: HikariTransactor[Task],
  skuRepo: SkuRepo,
  spuRepo: SpuRepo,
  skuDetailRepo: SkuDetailRepo,
  skuSpecDetailRepo: SkuSpecDetailRepo
) extends ProductService {

  override def getSkuAndSpecs(skuId: Long): DomainIO[(Sku, List[SkuSpecDetail])] = {
    for {
      sku <- skuRepo.findBySkuId(skuId).convertWithXa(xa).someOrFail(ProductError.SkuNotFound)
      specs <- skuSpecDetailRepo.listSpecsBySkuId(skuId).convertWithXa(xa)
    } yield (sku, specs)
  }


  override def homePageList(pageNum: Int, pageSize: Int): DomainIO[PageResponseDto[SkuCardDto]] = {
    
    val tx = for {
      items <- skuDetailRepo.listPageSortedByTime(pageNum, pageSize)
      totalSize <- skuRepo.pageTotalSize
    } yield (items, totalSize)
    
    for {
      (items, totalSize) <- tx.convertWithXa(xa)
      its = items.map(SkuCardDto.fromSkuDetail)
    } yield PageResponseDto(its, pageNum, pageSize, totalSize)
  }


  override def getStock(skuId: Long): DomainIO[Int] =
    skuRepo.findStockBySkuId(skuId).convertWithXa(xa)
      .someOrFail(ProductError.SkuNotFound)

  override def getSpecs(skuId: Long): DomainIO[List[SkuSpecDetail]] =
    skuSpecDetailRepo.listSpecsBySkuId(skuId).convertWithXa(xa)

  override def getSkuDetail(skuId: Long): DomainIO[SkuDetail] =
    skuDetailRepo.findSkuDetailById(skuId).convertWithXa(xa)
      .someOrFail(ProductError.SkuNotFound)


  override def getSkuIds4Spu(spuId: Long): DomainIO[List[Long]] =
    spuRepo.listById(spuId).convertWithXa(xa)

  override def getSpu(skuId: Long): DomainIO[Spu] =
    spuRepo.findById(skuId).convertWithXa(xa).someOrFail(ProductError.SpuNotFound)

  override def getSkus4Spu(spuId: Long): DomainIO[List[(Sku, List[SkuSpecDetail])]] =
    for {
      skuIds <- getSkuIds4Spu(spuId)
      s <- ZIO.foreach(skuIds)(getSkuAndSpecs)
    } yield s


}


